// YamlLanguageService.cs
//
// Copyright 2008 by Burt Harris
// Licensed under the Microsoft Public License (Ms-PL)
// See: http://www.codeplex.com/yaml/license
//
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Runtime.InteropServices;
using Microsoft.Scripting;
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.Package;
using Microsoft.VisualStudio.TextManager.Interop;

namespace YaTools.Yaml.LanguagePackage
{
    [Guid("232CBE4F-1D39-463b-A160-FFBB8D8880C0")]
    internal class YamlLanguageService : LanguageService
    {
        internal const string Guid = "232CBE4F-1D39-463b-A160-FFBB8D8880C0";
        private static YamlLanguagePreferences _languagePreferences;
        private IdleHandler _idleHandler;

        public override string Name
        {
            get { return Resources.YamlLanguageName; }
        }

        public override LanguagePreferences
            GetLanguagePreferences()
        {
            if (_languagePreferences == null)
            {
                _languagePreferences = new YamlLanguagePreferences(Site,
                                            typeof (YamlLanguageService).GUID, "YAML");
                _languagePreferences.Init();
            }
            return _languagePreferences;
        }

        public override void Initialize()
        {
            base.Initialize();

            // Setup an idle handler to trigger background parsing for error checking.
            // Our base class'es OnIdle method will take care of notifying 
            // any YamlSources on their OnIdle() method.

            _idleHandler = new IdleHandler(Site, OnIdle);
        }

        public override void Dispose()
        {
            try
            {
                if (_idleHandler != null)
                    _idleHandler.Dispose();
                _idleHandler = null;
            }
            finally
            {
                base.Dispose();
            }
            GC.SuppressFinalize(this);
        }


        public override string GetFormatFilterList()
        {
            return "YAML File (*.yaml)\n*.yaml\nJSON File (*.json)\n*.json\n";
        }

        public override IScanner GetScanner(IVsTextLines buffer)
        {
            throw new NotImplementedException();
        }

        public override AuthoringScope ParseSource(ParseRequest req)
        {
            req.Scope = new YamlAuthoringScope();
            try
            {
                switch (req.Reason)
                {
                    case ParseReason.HighlightBraces:
                        ParseSourceForBraces(req);
                        break;
                    case ParseReason.Check:
                        ParseSourceForCheck(req);
                        break;
                }
                return req.Scope;
            }
            catch (Exception ex)
            {
                // So far, the only exception I've caught here
                // was a thread abort exception, caused by an
                // infinate loop that VS decided to kill when
                // I closed the window.

                Trace.WriteLine(ex, "ParseError");
                throw;
            }
        }

        private void ParseSourceForCheck(ParseRequest req)
        {
            // Get the source object for this file

            Source source = GetSource(req.FileName);
            AuthoringSink authoringSink = req.Sink;

            YamlParser parser = new MyYamlParser(source);
            parser.ErrorSink = new VsErrorSink(authoringSink, req.FileName);

            parser.ParseDocument();
        }

        private static void ParseSourceForBraces(ParseRequest req)
        {
            // Get the source object for this file

            AuthoringSink authoringSink = req.Sink;

            var scanner = new YamlScanner();
            scanner.SetSource(req.Text);
            scanner.ErrorSink = new VsErrorSink(authoringSink, req.FileName);

            var braceStack = new Stack<TextSpan>();

            // Temporarily disable parsing
            // return authoringScope;

            while (true)
            {
                Token token = scanner.NextToken();

                switch (token)
                {
                    case Token.EndOfStream:
                        return;

                    case Token.OpenBracket:
                    case Token.OpenBrace:
                        braceStack.Push(scanner.TokenSpan.TextSpan());
                        break;

                    case Token.CloseBracket:
                        TextSpan open = braceStack.Pop();
                        TextSpan close = scanner.TokenSpan.TextSpan();
                        authoringSink.MatchPair(open, close, 10);
                        break;

                    case Token.CloseBrace:
                        open = braceStack.Pop();
                        close = scanner.TokenSpan.TextSpan();
                        authoringSink.MatchPair(open, close, 10);
                        break;
                }
            }
        }

        public override Source CreateSource(IVsTextLines buffer)
        {
            return YamlSource.Create(this, buffer);
        }

        public override Colorizer GetColorizer(IVsTextLines buffer)
        {
            Source source = GetOrCreateSource(buffer);
            return source.GetColorizer();
        }

        #region Custom Colors

        // Add some subtlty to the default colors where possible

        private static readonly Dictionary<COLORINDEX, Color> CustomColors 
            = new Dictionary<COLORINDEX, Color>
            {
                {
                    COLORINDEX.CI_BROWN,
                    Color.DarkOrange
                    },
                // YAML orange
                {
                    COLORINDEX.CI_LIGHTGRAY,
                    Color.FromArgb(239, 239, 239)
                    },
                // Very very light gray
                {
                    COLORINDEX.CI_AQUAMARINE,
                    Color.FromArgb(200, 255, 225)
                    },
                // Pale green
                {COLORINDEX.CI_MAROON, Color.Teal},
                // Tags...
                {COLORINDEX.CI_RED, Color.Pink},
            };

        private static Color Lookup(COLORINDEX index)
        {
            Color color;
            CustomColors.TryGetValue(index, out color);
            return color;
        }

        //private static ColorableItem Ci(string name, COLORINDEX fgIndex, Color fgColor, COLORINDEX bgIndex, Color backgroundColor)
        //{
        //    return new ColorableItem(name, "YAML " + name, fgIndex, bgIndex,
        //        fgColor, backgroundColor, FONTFLAGS.FF_DEFAULT);
        //}

        private static ColorableItem Colors(string name, COLORINDEX fgIndex, COLORINDEX bgIndex)
        {
            return new ColorableItem(name, "YAML " + name, fgIndex, bgIndex,
                                     Lookup(fgIndex), Lookup(bgIndex), FONTFLAGS.FF_DEFAULT);
        }

        private static ColorableItem Foreground(string name, COLORINDEX fgIndex)
        {
            return Colors(name, fgIndex, COLORINDEX.CI_USERTEXT_BK);
        }

        public override int GetItemCount(out int count)
        {
            count = (int) YamlColor.Count - 1;
            return VSConstants.S_OK;
        }

        public override int GetColorableItem(int index, out IVsColorableItem item)
        {
            switch ((YamlColor) index)
            {
                default:
                    item = null;
                    return VSConstants.E_INVALIDARG;

                case YamlColor.Comment:
                    item = Foreground("Comment", COLORINDEX.CI_DARKGREEN);
                    break;

                case YamlColor.Document:
                    item = Foreground("Document metadata", COLORINDEX.CI_BROWN);
                    break;

                case YamlColor.Escaped:
                    item = Foreground("Escaped characters", COLORINDEX.CI_BROWN);
                    break;

                case YamlColor.ImpliedKey:
                    item = Foreground("Implied key", COLORINDEX.CI_BLUE);
                    break;

                case YamlColor.Plain:
                    item = Foreground("Plain Scalar", COLORINDEX.CI_USERTEXT_FG);
                    break;

                case YamlColor.Indent:
                    item = Colors("Indent", COLORINDEX.CI_BROWN, COLORINDEX.CI_LIGHTGRAY);
                    break;

                case YamlColor.Indicator:
                    item = Foreground("Indicator", COLORINDEX.CI_BROWN);
                    break;

                case YamlColor.QuotationMark:
                    item = Foreground("Quotation marks", COLORINDEX.CI_BROWN);
                    break;

                case YamlColor.Quoted:
                    item = Colors("QuotedText", COLORINDEX.CI_USERTEXT_FG, COLORINDEX.CI_AQUAMARINE);
                    break;

                case YamlColor.Unscanned:
                    item = Foreground("Unscanned", COLORINDEX.CI_LIGHTGRAY);
                    break;

                case YamlColor.Inconsistant:
                    item = Colors("Inconsistant indent", COLORINDEX.CI_USERTEXT_BK, COLORINDEX.CI_RED);
                    break;

                case YamlColor.Anchor:
                    item = Foreground("Anchor", COLORINDEX.CI_DARKGRAY);
                    break;

                case YamlColor.Alias:
                    item = Foreground("Alias", COLORINDEX.CI_DARKGRAY);
                    break;

                case YamlColor.Tag:
                    item = Foreground("Tag", COLORINDEX.CI_MAROON);
                    break;
            }
            return VSConstants.S_OK;
        }

        #endregion

        #region Nested type: MyYamlParser

        private class MyYamlParser : YamlParser
        {
            public MyYamlParser(Source source)
                : base(new YamlScanner(), new Emitter())
            {
                Emitter.Initialize(new MySerializationEventSink());
                SetSource(YamlLanguage.DefaultScriptEngine
                              .CreateScriptSourceFromString(source.GetText(),
                                                            source.GetFilePath(), SourceCodeKind.File));
            }

            #region Nested type: MySerializationEventSink

            private class MySerializationEventSink : Sink
            {
                public override Sink BeginMap(Emitter emitter)
                {
                    return this;
                }

                public override Sink BeginSeq(Emitter emitter)
                {
                    return this;
                }

                public override object EndCollection(Emitter emitter)
                {
                    return null;
                }

                public override object AddScalar(Emitter emitter, string value)
                {
                    return null;
                }

                public override object AddAlias(Emitter emitter, string anchor)
                {
                    return null;
                }

                public override bool BeginObject(Emitter emitter, object value)
                {
                    return true;
                }

                public override void EndObject(Emitter emitter, object value)
                {
                }
            }

            #endregion
        }

        #endregion

        #region Nested type: SpanInfo

        internal struct SpanInfo
        {
            private readonly TextSpan _span;
            private readonly Token _token;

            internal SpanInfo(YamlScanner colorizer)
            {
                _token = colorizer.Token;
                _span.iStartLine = colorizer.TokenStart.Line - 1;
                _span.iStartIndex = colorizer.TokenStart.Column - 1;
                _span.iEndLine = colorizer.TokenEnd.Line - 1;
                _span.iEndIndex = colorizer.TokenEnd.Column - 1;
            }

            public Token Token
            {
                get { return _token; }
            }

            public TextSpan TextSpan
            {
                get { return _span; }
            }
        }

        #endregion
    }
}